
#include <iostream>
#include <sstream>

#include <tcbdb.h>
#include <tcutil.h>
#include <tchdb.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>

#include "bocat/common/ini.hpp"
#include "bocat/common/logging.hpp"

#include "binindex.hpp"

namespace	aptcab	{


#define	DEFAULT_LMEMB	1024
#define	DEFAULT_NMEMB	2046

#define	DEFAULT_LCNUM	8192
#define	DEFAULT_NCNUM	2048

#define	DEFAULT_XMSIZ	67108864


///////////////////////////////////////////////////////////////////////
//


class   TCHIndex	:	public TCAIndex
{
	typedef TCHDB * dbp_type;

public  :
    TCHIndex( const char* ini_filename, const char* ini_section )
		:	
			_rcnum( DEFAULT_RCNUM ),
			_bnum( DEFAULT_BNUM ),
			_dbp( NULL )
	{
		parseConfigure( ini_filename, ini_section );

		_dbp = tchdbnew();

		_db_opened = false;

		bocat::common::Logger*	logger_p = bocat::common::getLogger();
		logger_p->info( __LINE__, __FILE__, "open TokyoCabinet %s ...", _db_file.c_str() );
		logger_p->info( __LINE__, __FILE__, "  TokyoCabinet Version : %s", tcversion );
		logger_p->info( __LINE__, __FILE__, "  tch.bnum = %lld", _bnum );
		logger_p->info( __LINE__, __FILE__, "  tch.rcnum = %d", _rcnum );

		// dbtune
		int8_t	apow = 4;	// [ 0 .. 16], default: 4
		int8_t	fpow = 10;	// [ 0 .. 20], default: 10
		uint8_t	opts = 0;
		if ( !tchdbtune(_dbp, _bnum, apow, fpow, opts) ) {
			std::ostringstream	msg;
			int	ecode = tchdbecode( _dbp );
			msg << "fail to dbtune TokeyCabinet " << " -- " << tchdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		// dbcache
		if ( !tchdbsetcache(_dbp, _rcnum) ) {
			std::ostringstream	msg;
			int	ecode = tchdbecode( _dbp );
			msg << "fail to setcache TokeyCabinet (rcnum=" << _rcnum << ") -- " << tchdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		if ( !tchdbopen(_dbp, _db_file.c_str(), HDBOWRITER | HDBOCREAT) ){
			std::ostringstream	msg;
			int	ecode = tchdbecode( _dbp );
			msg << "fail to open TokeyCabinet '" << _db_file << "' -- " << tchdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}
		_db_opened = true;
	}

    virtual	~TCHIndex()
	{
		if ( _db_opened ) {
			tchdbclose( _dbp );

			_db_opened = false;
		}

		if ( _dbp ) {
			tchdbdel( _dbp );
			_dbp = NULL;
		}
	}

public  :
    int put( const std::string& skey,
             RecordLocation&  loc )
	{
		if ( !tchdbput( _dbp, skey.c_str(), skey.size(), &loc, sizeof(RecordLocation) ) ) {
			std::ostringstream	msg;

			int	ecode = tchdbecode( _dbp );
			msg << "fail to insert data -- "  << tchdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}

    int get( const std::string& skey,
                RecordLocation& loc )
	{
		int	rc = tchdbget3( _dbp, skey.c_str(), skey.size(), &loc, sizeof(RecordLocation) );
		if ( rc < 0 ) {
			// NOT found
			return	0;
		}
		
		if ( rc != sizeof(RecordLocation) ) {
			std::ostringstream	msg;

			int	ecode = tchdbecode( _dbp );
			msg << "fail to find key -- "  << tchdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}


    int erase( const std::string& skey )
	{
		if ( !tchdbout( _dbp, skey.c_str(), skey.size() ) ) {
			std::ostringstream	msg;

			int	ecode = tchdbecode( _dbp );
			msg << "fail to delete key -- "  << tchdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}

private	:
	void	parseConfigure( const char* ini_filename, const char* ini_section )
	{
		bocat::common::INIFile	ini( ini_filename );

		std::string	SECTION( ini_section );

		_db_file = ini[ SECTION ][ "dbfile" ];
		if ( _db_file.size() < 1 ) {
			std::ostringstream	msg;
			msg << "No 'dbfile' configured in [" << SECTION << "] of " << ini_filename;

			throw std::runtime_error( msg.str() );
		}

		std::string	xmsiz = ini[ SECTION ][ "tch.xmsiz" ];
		if ( xmsiz.size() < 1 ) {
			_xmsiz = DEFAULT_XMSIZ;
		}
		else {
			_xmsiz = boost::lexical_cast< int64_t >( xmsiz.c_str() );
		}

		std::string	bnum = ini[ SECTION ][ "tch.bnum" ];
		if ( bnum.size() < 1 ) {
			_bnum = DEFAULT_BNUM;
		}
		else {
			_bnum = boost::lexical_cast< int64_t >( bnum.c_str() );
		}

		std::string	rcnum = ini[ SECTION ][ "tch.rcnum" ];
		if ( rcnum.size() < 1 ) {
			_rcnum = DEFAULT_LCNUM;
		}
		else {
			_rcnum = boost::lexical_cast< int32_t >( rcnum.c_str() );
		}

	}


private :
    std::string _db_file;

    int32_t     _rcnum;
    int64_t     _bnum;
	int64_t		_xmsiz;

    dbp_type    _dbp;

    bool        _db_opened;

};	//// class TCHIndex


///////////////////////////////////////////////////////////////////////
//



class   TCBIndex	:	public TCAIndex
{
	typedef TCBDB * dbp_type;

public  :
    TCBIndex( const char* ini_filename, const char* ini_section )
		:	_dbp( NULL )
	{
		parseConfigure( ini_filename, ini_section );

		_dbp = tcbdbnew();

		_db_opened = false;

		bocat::common::Logger*	logger_p = bocat::common::getLogger();
		logger_p->info( __LINE__, __FILE__, "open TokyoCabinet %s ...", _db_file.c_str() );
		logger_p->info( __LINE__, __FILE__, "  TokyoCabinet Version : %s", tcversion );
		logger_p->info( __LINE__, __FILE__, "  tcb.bnum = %lld", _bnum );
		logger_p->info( __LINE__, __FILE__, "  tcb.lmemb = %d", _lmemb );
		logger_p->info( __LINE__, __FILE__, "  tcb.nmemb = %d", _nmemb );
		logger_p->info( __LINE__, __FILE__, "  tcb.lcnum = %d", _lcnum );
		logger_p->info( __LINE__, __FILE__, "  tcb.ncnum = %d", _ncnum );


		// dbtune
		//bool tcbdbtune(TCBDB *bdb, int32_t lmemb, int32_t nmemb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
		//int32_t	lmemb = 1024;	// [ 4 .. ), default: 128
		//int32_t	nmemb = 2048;	// [ 4 .. ), default: 256
		int8_t	apow = 4;		// [ 0 .. 16], default: 4
		int8_t	fpow = 10;		// [ 0 .. 20], default: 10
		uint8_t	opts = 0;
		if ( !tcbdbtune(_dbp, _lmemb, _nmemb, _bnum, apow, fpow, opts) ) {
			std::ostringstream	msg;
			int	ecode = tcbdbecode( _dbp );
			msg << "fail to dbtune TokeyCabinet " << " -- " << tcbdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		// dbcache
		//bool tcbdbsetcache(TCBDB *bdb, int32_t lcnum, int32_t ncnum);
		//int32_t	lcnum = 8192;	//// 1024;
		//int32_t	ncnum = 2048;	//// 512;
		if ( !tcbdbsetcache(_dbp, _lcnum, _ncnum) ) {
			std::ostringstream	msg;
			int	ecode = tcbdbecode( _dbp );
			msg << "fail to setcache TokeyCabinet (lcnum=" << _lcnum << ", ncnum=" << _ncnum << ") -- " << tcbdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		if ( !tcbdbopen(_dbp, _db_file.c_str(), HDBOWRITER | HDBOCREAT) ){
			std::ostringstream	msg;
			int	ecode = tcbdbecode( _dbp );
			msg << "fail to open TokeyCabinet '" << _db_file << "' -- " << tcbdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}
		_db_opened = true;
	}

    virtual	~TCBIndex()
	{
		if ( _db_opened ) {
			tcbdbclose( _dbp );

			_db_opened = false;
		}

		if ( _dbp ) {
			tcbdbdel( _dbp );
			_dbp = NULL;
		}
	}

public  :
    int put( const std::string& skey,
             RecordLocation&  loc )
	{
		if ( !tcbdbput( _dbp, skey.c_str(), skey.size(), &loc, sizeof(RecordLocation) ) ) {
			std::ostringstream	msg;

			int	ecode = tcbdbecode( _dbp );
			msg << "fail to insert data -- "  << tcbdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}

    int get( const std::string& skey,
                RecordLocation& loc )
	{
		int	sz = 0;
		const void*	p = tcbdbget3( _dbp, skey.c_str(), skey.size(), &sz );
		if ( NULL == p ) {
			// NOT found
			return	0;
		}
		
		if ( sz != sizeof(RecordLocation) ) {
			std::ostringstream	msg;

			int	ecode = tcbdbecode( _dbp );
			msg << "fail to find key -- "  << tcbdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		memcpy( &loc, p, sizeof(RecordLocation) );

		return	0;
	}


    int erase( const std::string& skey )
	{
		if ( !tcbdbout( _dbp, skey.c_str(), skey.size() ) ) {
			std::ostringstream	msg;

			int	ecode = tcbdbecode( _dbp );
			msg << "fail to delete key -- "  << tcbdberrmsg(ecode);

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}

private	:
	void	parseConfigure( const char* ini_filename, const char* ini_section )
	{
		bocat::common::INIFile	ini( ini_filename );

		std::string	SECTION( ini_section );

		_db_file = ini[ SECTION ][ "dbfile" ];
		if ( _db_file.size() < 1 ) {
			std::ostringstream	msg;
			msg << "No 'dbfile' configured in [" << SECTION << "] of " << ini_filename;

			throw std::runtime_error( msg.str() );
		}

		std::string	xmsiz = ini[ SECTION ][ "tcb.xmsiz" ];
		if ( xmsiz.size() < 1 ) {
			_xmsiz = DEFAULT_XMSIZ;
		}
		else {
			_xmsiz = boost::lexical_cast< int64_t >( xmsiz.c_str() );
		}

		std::string	bnum = ini[ SECTION ][ "tcb.bnum" ];
		if ( bnum.size() < 1 ) {
			_bnum = DEFAULT_BNUM;
		}
		else {
			_bnum = boost::lexical_cast< int64_t >( bnum.c_str() );
		}

		std::string	lmemb = ini[ SECTION ][ "tcb.lmemb" ];
		if ( lmemb.size() < 1 ) {
			_lmemb = DEFAULT_LMEMB;
		}
		else {
			_lmemb = boost::lexical_cast< int32_t >( lmemb.c_str() );
		}

		std::string	nmemb = ini[ SECTION ][ "tcb.nmemb" ];
		if ( nmemb.size() < 1 ) {
			_nmemb = DEFAULT_NMEMB;
		}
		else {
			_nmemb = boost::lexical_cast< int32_t >( nmemb.c_str() );
		}

		std::string	lcnum = ini[ SECTION ][ "tcb.lcnum" ];
		if ( lcnum.size() < 1 ) {
			_lcnum = DEFAULT_LCNUM;
		}
		else {
			_lcnum = boost::lexical_cast< int32_t >( lcnum.c_str() );
		}

		std::string	ncnum = ini[ SECTION ][ "tcb.ncnum" ];
		if ( ncnum.size() < 1 ) {
			_ncnum = DEFAULT_NCNUM;
		}
		else {
			_ncnum = boost::lexical_cast< int32_t >( ncnum.c_str() );
		}
	}


private :
    std::string _db_file;

	int32_t		_lmemb, _nmemb;
    int32_t     _lcnum, _ncnum;
    int64_t     _bnum;
    int64_t     _xmsiz;

    dbp_type    _dbp;

    bool        _db_opened;

};	//// class TCBIndex



///////////////////////////////////////////////////////////////////////
//


BinIndex::BinIndex( const char* ini_filename, const char* ini_section )
		:	_tc_p( NULL )
{
	//_tc_p = new TCHIndex( db_file, rcnum, bnum );
	_tc_p = new TCBIndex( ini_filename, ini_section );
}

BinIndex::~BinIndex()
{
	if ( NULL != _tc_p ) {
		delete _tc_p;

		_tc_p = NULL;
	}
}


///////////////////////////////////////////////////////////////////////
//

int 
BinIndex::put( const std::string& skey,
			   RecordLocation&  loc )
{
	return	_tc_p->put( skey, loc );
}

int 
BinIndex::get( const std::string& skey,
			   RecordLocation& loc )
{
	return	_tc_p->get( skey, loc );
}

int 
BinIndex::erase( const std::string& skey )
{
	return	_tc_p->erase( skey );
}

///////////////////////////////////////////////////////////////////////
//


}	//// namespace aptcab


